一、Loader(加载器)
作用:Webpack 本质上只能处理 JavaScript 和 JSON 文件,Loader 的作用是将非 JavaScript 模块(如 CSS、图片、TypeScript 等)转换为 Webpack 可识别的模块,以便进行后续处理(如打包、依赖分析)。
工作原理:Loader 是一个函数,接收源文件内容作为参数,经过处理后返回新的内容(或转换后的模块代码)。Loader 支持链式调用,上一个 Loader 的输出会作为下一个 Loader 的输入,执行顺序是从右到左(或从下到上)。
底层实现逻辑
- Webpack 在解析模块时,根据配置的
test规则匹配文件类型,找到对应的 Loader。 - 对匹配的文件,按顺序执行 Loader 链:每个 Loader 接收
content(文件内容)、map(source-map 信息)、meta(元数据)三个参数,处理后返回新的content或{ content, map, meta }对象。 - 最终经过所有 Loader 处理后,输出 Webpack 可识别的 JavaScript 代码。
手写一个简单的 Loader(例如:替换文本中的特定字符串)
需求:将文件中所有的 替换为 webpack。
创建 Loader 文件
replace-loader.js:javascript// loader 是一个函数,接收源文件内容作为参数 module.exports = function (content) { // 处理逻辑:替换字符串 const result = content.replace(/{{name}}/g, 'webpack'); // 返回处理后的内容(必须是字符串或 Buffer) return result; };在 Webpack 配置中使用:
javascript// webpack.config.js module.exports = { module: { rules: [ { test: /\.txt$/, // 匹配 .txt 文件 use: './replace-loader.js' // 使用自定义 Loader } ] } };测试:创建
test.txt,内容为Hello !,打包后会被转换为Hello webpack!。
二、Plugin(插件)
作用:Plugin 用于扩展 Webpack 的功能,解决 Loader 无法处理的问题(如打包优化、资源管理、环境变量注入等)。Plugin 可以介入 Webpack 打包的整个生命周期(如编译开始、模块解析、输出文件等)。
工作原理:Plugin 是一个带有 apply 方法的类(或对象),通过在 Webpack 生命周期的钩子(Hook)中注册回调函数,实现对打包过程的干预。
底层实现逻辑
- Webpack 运行时会创建一个 Compiler 实例,代表整个打包过程,包含所有配置和生命周期钩子。
- Plugin 通过
apply方法接收 Compiler 实例,在合适的钩子(如emit、done等)上注册回调。 - 当 Webpack 运行到对应阶段时,触发钩子回调,Plugin 即可执行自定义逻辑(如修改输出文件、添加资源等)。
手写一个简单的 Plugin(例如:打包完成后输出提示信息)
需求:在 Webpack 打包完成后,在控制台输出“打包成功!”和耗时。
创建 Plugin 文件
notify-plugin.js:javascriptclass NotifyPlugin { // apply 方法是 Plugin 的入口,接收 compiler 实例 apply(compiler) { // 注册 'done' 钩子(打包完成后触发) compiler.hooks.done.tap('NotifyPlugin', (stats) => { const time = stats.endTime - stats.startTime; // 计算耗时 console.log(`\n打包成功!耗时 ${time}ms`); }); } } module.exports = NotifyPlugin;在 Webpack 配置中使用:
javascript// webpack.config.js const NotifyPlugin = require('./notify-plugin.js'); module.exports = { plugins: [ new NotifyPlugin() // 实例化 Plugin 并添加到配置 ] };测试:运行打包命令,完成后控制台会输出提示信息和耗时。
三、Loader 与 Plugin 的核心区别
| 维度 | Loader | Plugin |
|---|---|---|
| 作用对象 | 处理特定类型的文件(模块) | 扩展 Webpack 整体功能(介入生命周期) |
| 实现形式 | 函数(接收内容,返回处理后内容) | 类(带 apply 方法,注册钩子) |
| 执行时机 | 模块解析阶段(加载文件时) | 整个打包周期(任意钩子阶段) |
通过上述示例可以看出,Loader 专注于“文件转换”,Plugin 专注于“流程扩展”,二者配合实现 Webpack 的灵活打包能力。
在 Webpack 中,Compiler 是核心对象之一,它代表了整个 Webpack 打包过程的生命周期管理者,包含了 Webpack 配置、插件钩子、编译状态等关键信息,是插件与 Webpack 交互的核心入口。
Compiler 的核心定位
Compiler由 Webpack 初始化时创建,全局唯一,贯穿整个打包过程(从启动到结束)。- 它保存了 Webpack 的完整配置(如
entry、output、module、plugins等),并提供了一系列生命周期钩子(Hooks),插件通过这些钩子介入打包流程。 - 简单说:
Compiler是 Webpack 的“大脑”,负责统筹整个打包过程,而插件通过Compiler才能感知和干预打包的各个阶段。
二、Compiler 的核心属性
Compiler 对象包含大量属性,核心常用的有:
| 属性名 | 作用 |
|---|---|
options | Webpack 完整配置对象(即 webpack.config.js 导出的配置),包含 entry、output、module 等。 |
hooks | 生命周期钩子集合(最核心属性),插件通过 compiler.hooks.xxx.tap() 注册回调,干预打包过程。 |
context | 项目根目录(绝对路径),通常对应 webpack.config.js 所在目录,可通过配置 context 字段修改。 |
outputPath | 输出目录的绝对路径(由 output.path 配置决定)。 |
inputFileSystem / outputFileSystem | 文件系统接口,用于读取输入文件和写入输出文件(默认使用 Node.js 的 fs 模块,可自定义实现)。 |
webpack | 指向 Webpack 模块本身,可用于访问 Webpack 内置工具函数(如 webpack.util)。 |
stats | 打包过程的统计信息对象(仅在打包完成后可用),包含模块数量、耗时、错误信息等。 |
三、Compiler 的核心能力(通过 hooks 实现)
Compiler 的核心能力体现在其 hooks 属性上,这些钩子对应 Webpack 打包的各个阶段,插件通过注册钩子回调实现自定义逻辑。常用钩子分类如下:
1. 初始化阶段
initialize:Webpack 初始化完成时触发(最早的钩子之一)。
2. 编译准备阶段
entryOption:处理entry配置后触发(可用于修改入口)。afterPlugins:所有插件初始化完成后触发。afterResolvers:解析器(resolver)初始化完成后触发(解析器用于查找模块路径)。
3. 编译阶段
beforeRun:编译开始前触发(compiler.run()执行前)。run:编译开始时触发。compile:创建Compilation对象前触发(Compilation代表一次编译过程,包含模块、依赖等信息)。thisCompilation:当前编译的Compilation对象创建后触发。compilation:Compilation对象创建且初始化完成后触发(插件常用,可操作模块和依赖)。
4. 输出阶段
emit:生成资源(如dist目录下的文件)到磁盘前触发(可修改输出内容)。afterEmit:资源输出到磁盘后触发。
5. 完成阶段
done:打包完全结束后触发(可获取统计信息,如输出成功提示)。failed:打包失败时触发(可捕获错误信息)。
四、如何使用 Compiler(插件视角)
插件通过 apply 方法接收 Compiler 实例,然后注册钩子回调。例如:
class MyPlugin {
apply(compiler) {
// 1. 访问配置
console.log('输出目录:', compiler.outputPath);
// 2. 注册钩子:打包完成后输出统计信息
compiler.hooks.done.tap('MyPlugin', (stats) => {
console.log('打包完成,模块数量:', stats.modules.length);
});
// 3. 注册钩子:输出资源前修改内容
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
// compilation 是当前编译的详情对象,包含输出的文件
for (const filename in compilation.assets) {
if (filename.endsWith('.js')) {
// 读取原内容
const content = compilation.assets[filename].source();
// 添加注释
const newContent = `/* 自定义注释 */\n${content}`;
// 更新资源
compilation.assets[filename] = {
source: () => newContent,
size: () => newContent.length
};
}
}
callback(); // 异步钩子需调用回调
});
}
}总结
Compiler 是 Webpack 的“全局控制器”,它:
- 保存了完整配置和运行时状态;
- 提供了覆盖整个打包生命周期的钩子;
- 是插件与 Webpack 交互的唯一入口。
理解 Compiler 的属性和钩子,是编写复杂 Webpack 插件的基础。